package com.gzdh.tang.config.weixin;

import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import okhttp3.HttpUrl;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import sun.misc.BASE64Decoder;

import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import static com.gzdh.tang.config.weixin.WXPayConstants.USER_AGENT;
import static com.gzdh.tang.config.weixin.WXPayUtil.generateNonceStr;


public class WXPay {

    private WXPayConfig config;
    private SignType signType;
    private boolean autoReport;
    private boolean useSandbox;
    private String notifyUrl;
    private WXPayRequest wxPayRequest;

    public WXPay(final WXPayConfig config) throws Exception {
        this(config, null, true, false);
    }

    public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
        this(config, null, autoReport, false);
    }

    public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception {
        this(config, null, autoReport, useSandbox);
    }

    public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
        this(config, notifyUrl, true, false);
    }

    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
        this(config, notifyUrl, autoReport, false);
    }

    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
        this.config = config;
        this.notifyUrl = notifyUrl;
        this.autoReport = autoReport;
        this.useSandbox = useSandbox;
        if (useSandbox) {
            this.signType = SignType.MD5; // 沙箱环境
        } else {
            this.signType = SignType.MD5;
        }
        this.wxPayRequest = new WXPayRequest(config);
    }

    private void checkWXPayConfig() throws Exception {
        if (this.config == null) {
            throw new Exception("config is null");
        }
        if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
            throw new Exception("appid in config is empty");
        }
        if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
            throw new Exception("appid in config is empty");
        }
        if (this.config.getCertStream() == null) {
            throw new Exception("cert stream in config is empty");
        }
        if (this.config.getWXPayDomain() == null) {
            throw new Exception("config.getWXPayDomain() is null");
        }

        if (this.config.getHttpConnectTimeoutMs() < 10) {
            throw new Exception("http connect timeout is too small");
        }
        if (this.config.getHttpReadTimeoutMs() < 10) {
            throw new Exception("http read timeout is too small");
        }

    }

    /**
     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
     * 该函数适用于商户适用于统一下单等接口，不适用于红包、代金券接口
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
//        if (WXPayUtils.getSubmchMode() == 1) {
//            reqData.put("sub_mch_id", WXPayUtils.getSubMchId());
//        }
        reqData.put("mch_id", this.config.getMchID());
        reqData.put("appid", this.config.getAppID());
        reqData.put("nonce_str", generateNonceStr());
        if (SignType.MD5.equals(this.signType)) {
            reqData.put("sign_type",  WXPayConstants.MD5);
        } else if (SignType.HMACSHA256.equals(this.signType)) {
            reqData.put("sign_type", WXPayConstants.HMACSHA256);
        }
        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
        return reqData;
    }

    public Map<String, String> fillRequestData2(Map<String, String> reqData) throws Exception {
        reqData.put("mch_id", config.getMchID());
        reqData.put("nonce_str", generateNonceStr());
        if (SignType.MD5.equals(this.signType)) {
            reqData.put("sign_type",  WXPayConstants.HMACSHA256);
        } else if (SignType.HMACSHA256.equals(this.signType)) {
            reqData.put("sign_type",  WXPayConstants.HMACSHA256);
        }
        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), SignType.HMACSHA256));
        System.out.println(reqData.toString());
        return reqData;
    }

    public Map<String, String> fillRequestData3(Map<String, String> reqData) throws Exception {
        reqData.put("mch_id", this.config.getMchID());
        reqData.put("appid", this.config.getAppID());
        reqData.put("nonce_str", generateNonceStr());
        reqData.put("sign_type",  WXPayConstants.HMACSHA256);
        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), SignType.HMACSHA256));
        return reqData;
    }

    public Map<String, String> fillRequestData4(Map<String, String> reqData) throws Exception {
        reqData.put("mch_id", this.config.getMchID());
        if (WXPayUtils.getSubmchMode() == 1) {
            reqData.put("sub_mch_id", WXPayUtils.getSubMchId());
        }
        reqData.put("nonce_str", generateNonceStr());
        reqData.put("sign_type", WXPayConstants.HMACSHA256);
        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), SignType.HMACSHA256));
        return reqData;
    }

    public Map<String, String> applyment(String openid, int price, String order) {
        Map<String, String> resultMap = new HashMap<>();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi");
        httpPost.setHeader("Content-type", "application/json");
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("User-Agent", USER_AGENT + " " + config.getMchID());

        CloseableHttpClient client = null;
        try {
            client = HttpClients.custom().setSSLContext(getSSLContext(this.config.getMchID())).build();
            Map<String, Object> tDate = new HashMap<>();
            tDate.put("sp_appid", config.getAppID());
            tDate.put("sp_mchid", config.getMchID());
            tDate.put("sub_mchid", config.getSubMchId());
            tDate.put("description", "test");
            tDate.put("out_trade_no", order);
            tDate.put("notify_url", "http://wxapi.gzdhkj.com/payok");
            Map<String, Object> amount = new HashMap<>();
            amount.put("total", price);
            tDate.put("amount", amount);
            Map<String, Object> payer = new HashMap<>();
            payer.put("sp_openid", openid);
            tDate.put("payer", payer);
            Map<String, Object> settle_info = new HashMap<>();
            settle_info.put("profit_sharing", true);
            tDate.put("settle_info", settle_info);
            String json = new JSONObject(tDate).toJSONString();
            System.out.println(json);
            InputStream certStream = config.getCertStream();
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, this.config.getMchID().toCharArray());
            Enumeration aliases = ks.aliases();
            String keyAlias = null;
            if (aliases.hasMoreElements()) {
                keyAlias = (String) aliases.nextElement();
                System.out.println("p12's alias----->" + keyAlias);
            }
            PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, this.config.getMchID().toCharArray());
            String privateKeyStr = org.apache.commons.codec.binary.Base64.encodeBase64String(privateKey.getEncoded());
            System.out.println("私钥------------->" + privateKeyStr);
//            //公钥
//            Certificate certificate = ks.getCertificate(keyAlias);
//            String publicKeyStr = Base64.encodeBase64String(certificate.getPublicKey().getEncoded());
//            System.out.println("公钥------------->"+publicKeyStr);
            StringEntity requestEntity = new StringEntity(json, "utf-8");
            requestEntity.setContentEncoding("UTF-8");
            httpPost.setEntity(requestEntity);
            String url = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
            HttpUrl httpUrl = HttpUrl.parse(url);
            httpPost.setHeader("Wechatpay-Serial", "1F156C77B76EC4BA26E0184808C2136A94F10308");
            httpPost.setHeader("Authorization",
                    "WECHATPAY2-SHA256-RSA2048" + " " +
                            getToken("POST", httpUrl,
                                    json, this.config.getMchID(),
                                    privateKeyStr
                                    , "1F156C77B76EC4BA26E0184808C2136A94F10308"));
            HttpResponse httpResponse = client.execute(httpPost);
            String prepay_id = "prepay_id=" + JSONObject.parseObject(EntityUtils.toString(httpResponse.getEntity())).getString("prepay_id");
            String times = String.valueOf(System.currentTimeMillis() / 1000);
            String norc = generateNonceStr();
            resultMap.put("appId", config.getAppID());
            resultMap.put("timeStamp", times);
            resultMap.put("nonceStr", norc);
            resultMap.put("package", prepay_id);
            String paySign = config.getAppID() + "\n" +
                    times + "\n" +
                    norc + "\n" +
                    prepay_id + "\n";
            resultMap.put("paySign", toSign(privateKeyStr, paySign));
            return resultMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

//    public Map<String, String> profitsharing(String transaction_id) {
//        Map<String, String> resultMap = new HashMap<>();
//        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/brand/profitsharing/orders");
//        httpPost.setHeader("Content-type", "application/json");
//        httpPost.setHeader("Accept", "application/json");
//        httpPost.setHeader("User-Agent", USER_AGENT + " " + config.getMchID());
//
//        CloseableHttpClient client = null;
//        try {
//            client = HttpClients.custom().setSSLContext(getSSLContext(this.config.getMchID())).build();
//
//
//            Map<String, Object> tDate = new HashMap<>();
//            tDate.put("brand_mchid", "1580836091");
//            tDate.put("appid", "wx15f2bc50aa9917fe");
//            tDate.put("sub_mchid", "1580836091");
//            tDate.put("transaction_id", "4200000827202010306861687195");
//            tDate.put("out_order_no", "20201030105201");
//            List<Object> receiversList = new ArrayList<>();
//            Map<String, Object> receivers = new HashMap<>();
//            receivers.put("type", "PERSONAL_WECHATID");
//            receivers.put("account", "yumeyume3400");
//            receivers.put("amount", 1);
//            receivers.put("description", "技术服务费");
//            receiversList.add(receivers);
//            tDate.put("receivers", receiversList);
//            tDate.put("finish", false);
//            String json = new JSONObject(tDate).toJSONString();
//            System.out.println(json);
//            InputStream certStream = config.getCertStream();
//            KeyStore ks = KeyStore.getInstance("PKCS12");
//            ks.load(certStream, this.config.getMchID().toCharArray());
//
//
//            Enumeration aliases = ks.aliases();
//            String keyAlias = null;
//            if (aliases.hasMoreElements()) {
//                keyAlias = (String) aliases.nextElement();
//                System.out.println("p12's alias----->" + keyAlias);
//            }
//            PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, this.config.getMchID().toCharArray());
//            String privateKeyStr = org.apache.commons.codec.binary.Base64.encodeBase64String(privateKey.getEncoded());
//            System.out.println("私钥------------->" + privateKeyStr);
//
//            StringEntity requestEntity = new StringEntity(json, "utf-8");
//            requestEntity.setContentEncoding("UTF-8");
//            httpPost.setEntity(requestEntity);
//            String url = "https://api.mch.weixin.qq.com/v3/brand/profitsharing/orders";
//            HttpUrl httpUrl = HttpUrl.parse(url);
//            httpPost.setHeader("Wechatpay-Serial", "1F156C77B76EC4BA26E0184808C2136A94F10308");
//            httpPost.setHeader("Authorization",
//                    "WECHATPAY2-SHA256-RSA2048" + " " +
//                            getToken("POST", httpUrl,
//                                    json, this.config.getMchID(),
//                                    privateKeyStr
//                                    , "1F156C77B76EC4BA26E0184808C2136A94F10308"));
//            HttpResponse httpResponse = client.execute(httpPost);
//            String strs = EntityUtils.toString(httpResponse.getEntity());
//            System.out.println(strs);
//            return resultMap;
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

//    public Map<String, String> profitsharingreceivers() {
//        Map<String, String> resultMap = new HashMap<>();
//        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/brand/profitsharing/receivers/add");
//        httpPost.setHeader("Content-type", "application/json");
//        httpPost.setHeader("Accept", "application/json");
//        httpPost.setHeader("User-Agent", USER_AGENT + " " + config.getMchID());
//
//        CloseableHttpClient client = null;
//        try {
//            client = HttpClients.custom().setSSLContext(getSSLContext(this.config.getMchID())).build();
//
//
//            Map<String, Object> tDate = new HashMap<>();
//            tDate.put("brand_mchid", config.getMchID());
//            tDate.put("appid", "wx15f2bc50aa9917fe");
//            tDate.put("type", "PERSONAL_WECHATID");
//            tDate.put("account", "yumeyume3400");
//            tDate.put("name", "周伯霖");
//            tDate.put("relation_type", "PLATFORM");
//            String json = new JSONObject(tDate).toJSONString();
//            System.out.println(json);
//            InputStream certStream = config.getCertStream();
//            KeyStore ks = KeyStore.getInstance("PKCS12");
//            ks.load(certStream, this.config.getMchID().toCharArray());
//
//
//            Enumeration aliases = ks.aliases();
//            String keyAlias = null;
//            if (aliases.hasMoreElements()) {
//                keyAlias = (String) aliases.nextElement();
//                System.out.println("p12's alias----->" + keyAlias);
//            }
//            PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, this.config.getMchID().toCharArray());
//            String privateKeyStr = org.apache.commons.codec.binary.Base64.encodeBase64String(privateKey.getEncoded());
//            System.out.println("私钥------------->" + privateKeyStr);
//
//            StringEntity requestEntity = new StringEntity(json, "utf-8");
//            requestEntity.setContentEncoding("UTF-8");
//            httpPost.setEntity(requestEntity);
//            String url = "https://api.mch.weixin.qq.com/v3/brand/profitsharing/receivers/add";
//            HttpUrl httpUrl = HttpUrl.parse(url);
//            httpPost.setHeader("Wechatpay-Serial", "1F156C77B76EC4BA26E0184808C2136A94F10308");
//            httpPost.setHeader("Authorization",
//                    "WECHATPAY2-SHA256-RSA2048" + " " +
//                            getToken("POST", httpUrl,
//                                    json, this.config.getMchID(),
//                                    privateKeyStr
//                                    , "1F156C77B76EC4BA26E0184808C2136A94F10308"));
//            HttpResponse httpResponse = client.execute(httpPost);
//            String strs = EntityUtils.toString(httpResponse.getEntity());
//            System.out.println(strs);
//            return resultMap;
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return null;
//    }

    /**
     * @param method
     * @param url
     * @param body
     * @param mchId      商户号
     * @param privateKey 商户证书私钥
     * @param serialNo   商户API证书序列号
     * @return
     * @throws Exception
     */
    public static String getToken(String method, HttpUrl url, String body, String mchId, String privateKey, String serialNo) throws Exception {
        String nonceStr = generateNonceStr();

        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        PrivateKey privateKey1 = getPrivateKey(privateKey);
        String signature = sign(message.getBytes("utf-8"), privateKey1);

        return "mchid=\"" + mchId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + serialNo + "\","
                + "signature=\"" + signature + "\"";
    }

    public String toSign(String privateKey, String message) throws Exception {
        PrivateKey privateKey1 = getPrivateKey(privateKey);
        return sign(message.getBytes("utf-8"), privateKey1);
    }

    private static String sign(byte[] message, PrivateKey privateKey) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(privateKey);
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    private static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }

        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes = new byte[0];
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // 指定加密算法
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        // 取私钥匙对象
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 获取证书内容
     *
     * @param mchId    商户号
     * @return
     */
    public SSLContext getSSLContext(String mchId) {
        InputStream inputStream;
        try {
            inputStream = this.config.getCertStream();
            if (inputStream == null) {
                throw new RuntimeException("证书为空，请核对证书");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        try {
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            char[] partnerId2charArray = mchId.toCharArray();

            keystore.load(inputStream, partnerId2charArray);
            return SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
        } catch (Exception var9) {
            var9.printStackTrace();
            throw new RuntimeException("证书文件有问题，请核实！", var9);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    public Map<String, String> testcrt() throws Exception {
        Map<String, String> reqData = new HashMap<>();
        String respXml = this.requestWithoutCert("/risk/getcertficates",
                this.fillRequestData2(reqData), 10000, 1000);
        return WXPayUtil.xmlToMap(respXml);
    }


    /**
     * 判断xml数据的sign是否有效，必须包含sign字段，否则返回false。
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
        // 返回数据的签名方式和请求中给定的签名方式是一致的
        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
    }

    /**
     * 判断支付结果通知中的sign是否有效
     *
     * @param reqData 向wxpay post的请求数据
     * @return 签名是否有效
     * @throws Exception
     */
    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
        String signTypeInData = reqData.get( WXPayConstants.FIELD_SIGN_TYPE);
        SignType signType;
        if (signTypeInData == null) {
            signType = SignType.MD5;
        } else {
            signTypeInData = signTypeInData.trim();
            if (signTypeInData.length() == 0) {
                signType = SignType.MD5;
            } else if ( WXPayConstants.MD5.equals(signTypeInData)) {
                signType = SignType.MD5;
            } else if ( WXPayConstants.HMACSHA256.equals(signTypeInData)) {
                signType = SignType.HMACSHA256;
            } else {
                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
            }
        }
        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
    }

    /**
     * 不需要证书的请求
     *
     * @param urlSuffix        String
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 超时时间，单位是毫秒
     * @param readTimeoutMs    超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String msgUUID = reqData.get("nonce_str");
        String reqBody = WXPayUtil.mapToXml(reqData);

        String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
        return resp;
    }

    /**
     * 需要证书的请求
     *
     * @param urlSuffix        String
     * @param reqData          向wxpay post的请求数据  Map
     * @param connectTimeoutMs 超时时间，单位是毫秒
     * @param readTimeoutMs    超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public String requestWithCert(String urlSuffix, Map<String, String> reqData,
                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String msgUUID = reqData.get("nonce_str");
        String reqBody = WXPayUtil.mapToXml(reqData);

        String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
        return resp;
    }

    /**
     * 处理 HTTPS API返回数据，转换成Map对象。return_code为SUCCESS时，验证签名。
     *
     * @param xmlStr API返回的XML格式数据
     * @return Map类型数据
     * @throws Exception
     */
    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
        String RETURN_CODE = "return_code";
        String return_code;
        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
        if (respData.containsKey(RETURN_CODE)) {
            return_code = respData.get(RETURN_CODE);
        } else {
            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
        }

        if (return_code.equals( WXPayConstants.FAIL)) {
            return respData;
        } else if (return_code.equals( WXPayConstants.SUCCESS)) {
            if (this.isResponseSignatureValid(respData)) {
                return respData;
            } else {
                throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
            }
        } else {
            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
        }
    }

    /**
     * 作用：提交刷卡支付<br>
     * 场景：刷卡支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：提交刷卡支付<br>
     * 场景：刷卡支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
        } else {
            url =  WXPayConstants.MICROPAY_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 提交刷卡支付，针对软POS，尽可能做成功
     * 内置重试机制，最多60s
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {
        return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
    }

    /**
     * 提交刷卡支付，针对软POS，尽可能做成功
     * 内置重试机制，最多60s
     *
     * @param reqData
     * @param connectTimeoutMs
     * @return
     * @throws Exception
     */
    public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {
        int remainingTimeMs = 60 * 1000;
        long startTimestampMs = 0;
        Map<String, String> lastResult = null;
        Exception lastException = null;

        while (true) {
            startTimestampMs = WXPayUtil.getCurrentTimestampMs();
            int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
            if (readTimeoutMs > 1000) {
                try {
                    lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
                    String returnCode = lastResult.get("return_code");
                    if (returnCode.equals("SUCCESS")) {
                        String resultCode = lastResult.get("result_code");
                        String errCode = lastResult.get("err_code");
                        if (resultCode.equals("SUCCESS")) {
                            break;
                        } else {
                            // 看错误码，若支付结果未知，则重试提交刷卡支付
                            if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
                                remainingTimeMs = remainingTimeMs - (int) (WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
                                if (remainingTimeMs <= 100) {
                                    break;
                                } else {
                                    WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
                                    if (remainingTimeMs > 5 * 1000) {
                                        Thread.sleep(5 * 1000);
                                    } else {
                                        Thread.sleep(1 * 1000);
                                    }
                                    continue;
                                }
                            } else {
                                break;
                            }
                        }
                    } else {
                        break;
                    }
                } catch (Exception ex) {
                    lastResult = null;
                    lastException = ex;
                }
            } else {
                break;
            }
        }

        if (lastResult == null) {
            throw lastException;
        } else {
            return lastResult;
        }
    }

    /**
     * 作用：统一下单<br>
     * 场景：公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：统一下单<br>
     * 场景：公共号支付、扫码支付、APP支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
        } else {
            url =  WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
        }
        if (this.notifyUrl != null) {
            reqData.put("notify_url", this.notifyUrl);
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用，发起支付分账
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharing(Map<String, String> reqData) throws Exception {

        if (WXPayUtils.getSubmchMode() == 1) {
            reqData.put("sub_mch_id", config.getSubMchId());
        }
        String respXml = this.requestWithCert("/secapi/pay/profitsharing",
                this.fillRequestData3(reqData), 10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用： 添加分账人
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharingaddreceiver(Map<String, String> reqData) throws Exception {

        if (WXPayUtils.getSubmchMode() == 1) {
            //存在子商户模式，则需要添加子商户记录
            reqData.put("sub_mch_id", WXPayUtils.getSubMchId());
        }
        String respXml = this.requestWithCert("/pay/profitsharingaddreceiver", this.fillRequestData3(reqData),
                10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用：分账查询
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharingquery(Map<String, String> reqData) throws Exception {
        String respXml = this.requestWithCert("/pay/profitsharingquery", this.fillRequestData4(reqData),
                10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用：完结分账
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharingfinish(Map<String, String> reqData) throws Exception {
        if (WXPayUtils.getSubmchMode() == 1) {
            reqData.put("sub_mch_id", config.getSubMchId());
        }
        String respXml = this.requestWithCert("/secapi/pay/profitsharingfinish", this.fillRequestData3(reqData),
                10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用： 分账比例
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharingmerchantratioquery(Map<String, String> reqData) throws Exception {
        if (WXPayUtils.getSubmchMode() == 1) {
            reqData.put("sub_mch_id", config.getSubMchId());
        }
        String respXml = this.requestWithCert("/pay/profitsharingmerchantratioquery", this.fillRequestData4(reqData),
                10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用：完结回退
     *
     * @param reqData
     * @return
     * @throws Exception
     */
    public Map<String, String> profitsharingreturn(Map<String, String> reqData) throws Exception {
        if (WXPayUtils.getSubmchMode() == 1) {
            reqData.put("sub_mch_id", config.getSubMchId());
        }
        String respXml = this.requestWithCert("/secapi/pay/profitsharingreturn", this.fillRequestData3(reqData),
                10000, 10000);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用：查询订单<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：查询订单<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData          向wxpay post的请求数据 int
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.ORDERQUERY_URL_SUFFIX;
        } else {
            url =  WXPayConstants.ORDERQUERY_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：撤销订单<br>
     * 场景：刷卡支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：撤销订单<br>
     * 场景：刷卡支付<br>
     * 其他：需要证书
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
        } else {
            url =  WXPayConstants.REVERSE_URL_SUFFIX;
        }
        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：关闭订单<br>
     * 场景：公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：关闭订单<br>
     * 场景：公共号支付、扫码支付、APP支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
        } else {
            url =  WXPayConstants.CLOSEORDER_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：申请退款<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：申请退款<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付<br>
     * 其他：需要证书
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
        } else {
            url =  WXPayConstants.REFUND_URL_SUFFIX;
        }
        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：退款查询<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：退款查询<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
        } else {
            url =  WXPayConstants.REFUNDQUERY_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：对账单下载（成功时返回对账单数据，失败时返回XML格式数据）<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：对账单下载<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付<br>
     * 其他：无论是否成功都返回Map。若成功，返回的Map中含有return_code、return_msg、data，
     * 其中return_code为`SUCCESS`，data为对账单数据。
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return 经过封装的API返回数据
     * @throws Exception
     */
    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
        } else {
            url =  WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
        }
        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
        Map<String, String> ret;
        // 出现错误，返回XML数据
        if (respStr.indexOf("<") == 0) {
            ret = WXPayUtil.xmlToMap(respStr);
        } else {
            // 正常返回csv数据
            ret = new HashMap<String, String>();
            ret.put("return_code",  WXPayConstants.SUCCESS);
            ret.put("return_msg", "ok");
            ret.put("data", respStr);
        }
        return ret;
    }

    /**
     * 作用：交易保障<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> report(Map<String, String> reqData) throws Exception {
        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：交易保障<br>
     * 场景：刷卡支付、公共号支付、扫码支付、APP支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
        } else {
            url =  WXPayConstants.REPORT_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return WXPayUtil.xmlToMap(respXml);
    }

    /**
     * 作用：转换短链接<br>
     * 场景：刷卡支付、扫码支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：转换短链接<br>
     * 场景：刷卡支付、扫码支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url =  WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
        } else {
            url =  WXPayConstants.SHORTURL_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

    /**
     * 作用：授权码查询OPENID接口<br>
     * 场景：刷卡支付
     *
     * @param reqData 向wxpay post的请求数据
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
    }

    /**
     * 作用：授权码查询OPENID接口<br>
     * 场景：刷卡支付
     *
     * @param reqData          向wxpay post的请求数据
     * @param connectTimeoutMs 连接超时时间，单位是毫秒
     * @param readTimeoutMs    读超时时间，单位是毫秒
     * @return API返回数据
     * @throws Exception
     */
    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
        String url;
        if (this.useSandbox) {
            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
        } else {
            url =  WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
        }
        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
        return this.processResponseXml(respXml);
    }

} // end class
